// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using System.Threading.Actors;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using System.Threading.Actors.Languages;
using System.Threading.Actors.Utilities;

namespace MyActorApp
{


    // A sample actor definition
    class MyActorType : FabricAdHocActorBase
    {
        public MyActorType(string actorName)
            : base(actorName)
        {
        }

        // Methods that are meant to be visible as actor methods need to be decorated with [ActorMethod],
        // need to return an object, and need to have two parameters, an IActorState and an object array.

        // A very simple method that does not store any state
        [ActorMethod]
        public static object SayHello(IActorState state, object[] parameters)
        {
            var result = "Hello!";
            state.Publish("HelloStream", result);
            return result;
        }

        // A slightly more complicated method that references a counter value that is stored
        // in the actor's state.
        [ActorMethod]
        public static object Bump(IActorState state, object[] parameters)
        {
            // Grab the amount to add from the parameter list
            var amountToAdd = (int)parameters[0];

            // Grab the current value of the counter
            int count = 0; // Default if this is the first access
            object temp;
            if (state.TryGet("_count", out temp))
            {
                count = temp.ToInt(); // compensate for possible js issue
            }

            // Increment the counter and store the new value
            count += amountToAdd;
            state.Set("_count", count);

            // And return the updated value
            return count;
        }

        // A dynamic version of the above
        [ActorMethod]
        public static object BumpD(dynamic state, dynamic[] parameters)
        {
            int amountToAdd = parameters[0];
            // Use ValueOrDefault when you don't know whether a property has been defined yet
            int currentCount = (int)state.ValueOrDefault("_count", 0);
            // Demonstrates "property" mode of dynamic state access
            state._count = currentCount + amountToAdd;
            // Return something in different format than non-dynamic version, so that
            // you can tell that this one was called.
            // Demonstrates calling state methods and "index" mode of dynamic state access
            return state.SayHello() + ":" + state["_count"];
        }

    }

    // A sample program using the sample actor
    // WARNING: Make sure that the program and the actor are in the same assembly.  Otherwise,
    // the actor methods will not be properly transferred to the running actor.
    class Program
    {
        // This will allow you to connect to a locally running actor runtime.
        // Make sure that the local cluster has been deployed and the "Empty"
        // actor app has been deployed onto the cluster.
        private static string s_connectionString = "127.0.0.1:19000";
        private static bool s_useGateway = false;

        //private static string s_connectionString = "<yourAzureClusterName>.cloudapp.net:19000";
        //private static bool s_useGateway = true;


        static void AddSmarts(FabricActorClient actorClient)
        {
            var asm = Assembly.GetExecutingAssembly();
            var name = asm.ManifestModule.Name;
            var location = asm.Location;
            var bytes = File.ReadAllBytes(location);
            actorClient.AddAssembly(name, bytes);

            // A javascript implementation of "Bump"
            actorClient.AddMethod(SupportedLanguages.Javascript,
                  "bumpJ",
                  @"
function bumpJ(state, amount)
{
    currval = 0;
    if(state.contains('_count')) currval = state.get('_count');
    newval = currval + amount;
    state.set('_count', newval);
    return newval;
}");

            // Yet another version of "Bump" -- this time in Python
            actorClient.AddMethod(SupportedLanguages.Python, 
                "BumpP", 
                @"
def BumpP(state, value):
    holder = clr.Reference[object]() # this is how we do 'out' parameters in IronPython
    currval = 0
    if state.TryGet('_count', holder):
        currval = holder.Value

    currval = currval + value;
    state.Set('_count', currval)
    return currval
");

            Console.WriteLine("\nAssembly loaded");

        }

        private static void Usage()
        {
            Console.WriteLine("My First Actor App");
            Console.WriteLine();
            Console.WriteLine("MyActorApp [-cluster connectionString] [-useGateway | -noGateway]");
            Console.WriteLine();
            Console.WriteLine("Valid connection strings include the address of the cluser and a port number,");
            Console.WriteLine("such as \"myservice.cloudapp.net:19000\" or \"localhost:19000\".");
            Console.WriteLine("Gateways are required for actors hosted in Azure, and are used by default.");
        }

        static void Main(string[] args)
        {
            // Create our actor
            var myActor = new MyActorType("myFirstActor");
            string connectionString = s_connectionString;
            bool useGateway = s_useGateway;
            if (args.Length > 0)
            {
                for (int argIndex = 0; argIndex < args.Length; argIndex++)
                {
                    if (String.Equals("-cluster", args[argIndex], StringComparison.InvariantCultureIgnoreCase))
                        connectionString = args[++argIndex];
                    else if (String.Equals("-useGateway", args[argIndex], StringComparison.InvariantCultureIgnoreCase))
                        useGateway = true;
                    else if (String.Equals("-noGateway", args[argIndex], StringComparison.InvariantCultureIgnoreCase))
                        useGateway = false;
                    else
                    {
                        Usage();
                        return;
                    }
                }
            }

            Console.WriteLine("Connecting to {0} {1}.", connectionString, useGateway ? "using a gateway" : "directly");
            var actorClient = myActor.DeployAndConnect(connectionString, useGateway, numReplicas: 3);

            // Now loop, allowing the user to enter actor commands and accompanying parameters
            // Special commands:
            //    --AddSmarts: Will ship assembly/methods to your actor
            //    --q: Quits the application
            // Parameters with a '#' prefix will be interpreted as integer parameters.
            while (true)
            {
                // 
                Console.Write("Enter an actor command (or AddSmarts or q): ");
                var line = Console.ReadLine();
                if (line.Equals("AddSmarts", StringComparison.InvariantCultureIgnoreCase))
                {
                    AddSmarts(actorClient);
                }
                else if(line.Equals("q", StringComparison.InvariantCultureIgnoreCase))
                {
                    break;
                }
                else if (string.IsNullOrEmpty(line))
                {
                    Console.WriteLine("I don't understand");
                }
                else
                {
                    // This is a call of an actor method
                    var parts = line.Trim().Split(' ');
                    var methodName = parts[0];
                    object[] methodArgs = new object[parts.Length - 1];
                    for (int i = 1; i < parts.Length; i++)
                    {
                        if (parts[i].StartsWith("#")) methodArgs[i - 1] = Int32.Parse(parts[1].Substring(1));
                        else methodArgs[i - 1] = parts[i];
                    }

                    try
                    {
                        var result = actorClient.CallMethod(methodName, methodArgs);
                        Console.WriteLine("result: {0}", result);
                    }
                    catch (Exception exc)
                    {
                        Exception baseException = (exc is AggregateException) ? ((AggregateException)exc).GetBaseException() : exc;
                        Console.WriteLine("exception: {0}", baseException.Message);
                    }
                }
            }
        }
    }
}
